#include "Character.h"
#include "GameMath.h"
#include "GameTest.h"

Character::Character()
{
    recoverTimer.waitTime = 0.1;
    recoverTimer.onTimeout = [this] {
        IncreaseHp(recoverHp);
        IncreaseMp(recoverMp);
    };
    rigidityTimer.waitTime = 2;
    rigidityTimer.onTimeout = [this] {
        canRigidity = true;
    };
}

void Character::OnUpdate(double delta)
{
    recoverTimer.OnUpdate(delta);
    rigidityTimer.OnUpdate(delta);
    UpdateMove(delta);
    animCurrent->OnUpdate(delta);
}

void Character::OnRender()
{
    animCurrent->OnRender(GAME_RENDERER, position);
    if (test_mode_flag) {
        RendererRect(position, GetShape(), {255, 0, 0, 255});
        auto t = GetAttackPosition();
        RendererRect(t.first, t.second, {111, 111, 255, 255});
    }
}

bool Character::CanRemove()
{
    return Object::CanRemove();
}

void Character::ResetStatus()
{
    isAttack = isRigidity = false;
}

void Character::IncreaseHp(double value)
{
    hp += value;
    if (hp >= maxHp) hp = maxHp;
}

void Character::DecreaseHp(double value)
{
    hp -= value;
    if (hp <= 0) hp = 0;
}

void Character::IncreaseMp(double value)
{
    mp += value;
    if (mp >= maxMp) mp = maxMp;
}

void Character::DecreaseMp(double value)
{
    mp -= value;
    if (mp <= 0) mp = 0;
}

void Character::Attack()
{
    if (isAttack || isRigidity || isDie) return;
    Mix_PlayChannel(-1, attackMix, 0);
    ResetStatus();
    animAttack[facing].Reset();
    animCurrent = &animAttack[facing];
    isAttack = true;
}

void Character::OnAttack(const std::vector<std::shared_ptr<Character>> &characters)
{
    if (GameMath::Sign(damage, 0) == 0) {
        return;
    }
    for (const auto& character : characters) {
        if (character->id == id) continue;
        auto attackPair = GetAttackPosition();
        if (GameMath::CollisionDetection(attackPair.first, attackPair.second,
                                         character->position, character->GetShape())) {
            character->DecreaseHp(damage);
            character->Rigidity();
        }
    }
    isAttack = false;
}

void Character::Die()
{
    if (isDie) return;
    Mix_PlayChannel(-1, dieMix, 0);
    ResetStatus();
    animDie[facing].Reset();
    animCurrent = &animDie[facing];
    isDie = true;
}

void Character::OnDie()
{
    isDie = false;
    isValid = false;
}

void Character::Rigidity()
{
    if (isDie || !canRigidity) return;
    ResetStatus();
    animRigidity[facing].Reset();
    animCurrent = &animRigidity[facing];
    canRigidity = false;
    isRigidity = true;
}

void Character::OnRigidity()
{
    isRigidity = false;
}

void Character::ReleaseSkill(uint16_t idx)
{}

void Character::UpdateMove(double delta)
{
    if (!isDie && GameMath::Sign(hp, 0) <= 0) {
        Die();
    }
    if (isAttack || isDie || isRigidity) {
        return;
    }
    velocity = direction.Normalize() * speed * SIZE_TILE;
    if (GameMath::Sign(velocity.Length(), 0) == 0) {
        animCurrent = &animStandBy[facing];
    } else {
        Facing w = Facing::Left, h = Facing::Up;
        if (direction.x > 0) w = Facing::Right;
        if (direction.y > 0) h = Facing::Down;
        if (std::fabs(direction.x) > std::fabs(direction.y)) facing = w;
        else facing = h;
        animCurrent = &animWalk[facing];
    }
    position += velocity * delta;
    if (GameInfo::CheckCollision(this)) {
        position -= velocity * delta;
    }
    GameInfo::Instance().InMap(position);
}

std::pair<GameVector, GameShape> Character::GetAttackPosition()
{
    GameVector vec = position;
    auto tempShape = attackShape;
    if (facing == Facing::Down || facing == Facing::Up) {
        tempShape = {attackShape.h, attackShape.w};
    }
    switch (facing) {
        case Facing::Left:
            vec.x -= attackShape.w / 2;
            break;
        case Facing::Right:
            vec.x += attackShape.w / 2;
            break;
        case Facing::Up:
            vec.y -= attackShape.w / 2;
            break;
        case Facing::Down:
            vec.y += attackShape.w / 2;
            break;
        default:
            break;
    }
    return {vec, tempShape};
}

